package com.rigsofrods.truckeditor.gui;

import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import javax.media.opengl.glu.GLU;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;

import com.rigsofrods.truckeditor.transforms3D.Camera;
import com.rigsofrods.truckeditor.transforms3D.Mat4;
import com.rigsofrods.truckeditor.transforms3D.Mat4Identity;
import com.rigsofrods.truckeditor.transforms3D.Mat4OrthoRH;
import com.rigsofrods.truckeditor.transforms3D.Mat4PerspRH;
import com.rigsofrods.truckeditor.transforms3D.Vec3D;
import com.rigsofrods.truckeditor.transforms3D.Camera.PresetView;

/**<h1>AbstractViewport</h1>
 * Class that takes care of camera and its control, but it doesn't do any rendering.<br>
 * @author AH
 *  */
public abstract class AbstractViewport extends JPanel implements ActionListener, ComponentListener{
	private static final long serialVersionUID = 5161962984235642603L;
	protected Mat4 mtxView = null;
	private Mat4[] mtxProjection;
	protected Camera camera = null;
	protected double orthProjectionScale = 1/100.0;
	protected Vec3D mouseCursor = new Vec3D(); 
	private Projection projection = Projection.PERSPECTIVE_PROJECTION;
	private int oldWidth = 0;
	private int oldHeight = 0;
	
	private static JPopupMenu popupMenu;

	public enum RenderingMode {
		WIREFRAME, PAINTED_WIREFRAME, SOLID
	}

	public enum Projection {
		ORTHOGONAL_PROJECTION, PERSPECTIVE_PROJECTION;
	}

	/*TODO: Following fields should go to the child class.*/
	public static Color colEdge = Color.DARK_GRAY;
	public static Color colSelectedEdge = Color.WHITE;
	public static Color colVertex = Color.LIGHT_GRAY;
	public static Color colBackground = new Color(0x7F7F7F);

	private RenderingMode renderingMode = RenderingMode.SOLID;
	public boolean displayWireframe = true;
	public boolean displayVertices = true;
	protected GLU glu = new GLU();

	// FIXME Pravouhle kamery jsou otocene smerem od objektu

	public AbstractViewport(int width, int height) {
		super();
		width = Math.max(width, getWidth());
		height = Math.max(height, getHeight());
		if (width < 1) width = 1;
		mtxProjection = new Mat4[]{
				new Mat4OrthoRH(width * orthProjectionScale, height * orthProjectionScale, 0.001f, 1000),
				new Mat4PerspRH(Math.PI / 4, height / (double) width, 0.001f, 1000)};
		camera = new Camera(PresetView.ISO_VIEW);
		camera.setFirstPerson(false);
		mtxView = camera.getViewMatrix();
		projection = Projection.PERSPECTIVE_PROJECTION;
		/*JPopupMenu puMenu = new JPopupMenu("Display");
		createViewportPopupMenu(puMenu);
		this.setComponentPopupMenu(puMenu);*/
		popupMenu = new JPopupMenu("Display");
		createViewportPopupMenu(popupMenu);
		setComponentPopupMenu(popupMenu);
		addComponentListener(this);
	}

	public void setCamera(Camera camera) {
		this.camera = camera;
		mtxView = camera.getViewMatrix();
	}

	public void setProjection(Projection projection) {
		this.projection = projection;
	}

	public RenderingMode getRenderingMode() {
		return renderingMode;
	}

	public void setRenderingMode(RenderingMode renderingMode) {
		this.renderingMode = renderingMode;
	}

	abstract public void drawScene();

	public Mat4 getProjectionMatrix() {
		return mtxProjection[projection.ordinal()];
	}

	public Mat4 getViewMatrix() {
		return mtxView;
	}

	/**
	 * Move camera position relative to its current position.
	 * @param xPrev Previous mouse position.
	 * @param yPrev Previous mouse position.
	 */
	public void moveCam(int xNew, int yNew, int xPrev, int yPrev) {
		//Flip y coordinates
		final float yn = (float)getHeight() - yNew - 1;
		final float yp = (float)getHeight() - yPrev - 1;
		
		float objPos[] = new float[8];
		float model[] = getViewMatrix().toFloatArray();
		float proj[] = getProjectionMatrix().toFloatArray();

		int view[] = new int[]{0, 0, getWidth(), getHeight()};
		Vec3D cPos = camera.getPos();

		//Project camera target position to get correct depth for reversed projection.
		glu.gluProject((float) cPos.x, (float) cPos.y, (float) cPos.z, model, 0, proj, 0, view, 0, objPos, 0);
		final float depth = objPos[2];
		//Unproject old and new mouse positions and move camera by half the distance of the resulted positions.
		glu.gluUnProject(xNew, yn, depth, model, 0, proj, 0, view, 0, objPos, 0);
		glu.gluUnProject(xPrev, yp, depth, model, 0, proj, 0, view, 0, objPos, 4);
		
		Vec3D posNew = new Vec3D(objPos[0], objPos[1], objPos[2]);
		Vec3D posPrev = new Vec3D(objPos[4], objPos[5], objPos[6]);
		
		Vec3D dv = posPrev.minus(posNew);
		mouseCursor = posNew;
		camera.move(dv.times(0.5));
		mtxView = camera.getViewMatrix();
	}

	public void orbitCam(int x, int y) {
		camera.addAzimuth(0.01 * (double) x);
		camera.addZenith(0.01 * (double) y);
		mtxView = camera.getViewMatrix();
		// if (projection == ORTHOGONAL_PROJECTION)
		// cameraName = camera.getViewName();
	}

	public void zoomCam(int dist) {
		// camera.addRadius(dist);
		camera.zoom(dist);
		orthProjectionScale *= Math.pow(1.01, dist);
		// if ((orthProjectionScale += dist/bimg.getHeight()) < 0.01)
		// orthProjectionScale = 0.01;
		mtxProjection[0] = new Mat4OrthoRH(getWidth()
				* orthProjectionScale, getHeight() * orthProjectionScale,
				0.001f, 1000);
		mtxView = camera.getViewMatrix();
	}
	
	private void createViewportPopupMenu(JComponent c) {
		JMenuItem menuItem;
		JMenu menu;
		
		menu = new JMenu("View");
	    menuItem = new JMenuItem("Perspective");
	    menuItem.setActionCommand("setProjection_" + AbstractViewport.Projection.PERSPECTIVE_PROJECTION.name());
	    menuItem.addActionListener(this);
	    menu.add(menuItem);
	    menuItem = new JMenuItem("Orthogonal");
	    menuItem.setActionCommand("setProjection_" + AbstractViewport.Projection.ORTHOGONAL_PROJECTION.name());
	    menuItem.addActionListener(this);
	    menu.add(menuItem);

	    menu.addSeparator();
	    menuItem = new JMenuItem("Front");
	    menuItem.setActionCommand("setView_" + Camera.FRONT_VIEW);
	    menuItem.addActionListener(this);
	    menu.add(menuItem);
	    menuItem = new JMenuItem("Back");
	    menuItem.setActionCommand("setView_" + Camera.BACK_VIEW);
	    menuItem.addActionListener(this);
	    menu.add(menuItem);
	    menuItem = new JMenuItem("Left");
	    menuItem.setActionCommand("setView_" + Camera.LEFT_VIEW);
	    menuItem.addActionListener(this);
	    menu.add(menuItem);
	    menuItem = new JMenuItem("Right");
	    menuItem.setActionCommand("setView_" + Camera.RIGHT_VIEW);
	    menuItem.addActionListener(this);
	    menu.add(menuItem);
	    menuItem = new JMenuItem("Top");
	    menuItem.setActionCommand("setView_" + Camera.TOP_VIEW);
	    menuItem.addActionListener(this);
	    menu.add(menuItem);
	    menuItem = new JMenuItem("Bottom");
	    menuItem.setActionCommand("setView_" + Camera.BOTTOM_VIEW);
	    menuItem.addActionListener(this);
	    menu.add(menuItem);    
	    c.add(menu);

		/*menu = new JMenu("Display");
	    menuItem = new JMenuItem("Wireframe");
	    menuItem.setActionCommand("setDisplayMode_" + AbstractViewport.RenderingMode.WIREFRAME.name());
	    menuItem.addActionListener(this);
	    menu.add(menuItem);
	    menuItem = new JMenuItem("Painted wireframe");
	    menuItem.setActionCommand("setDisplayMode_" + AbstractViewport.RenderingMode.PAINTED_WIREFRAME.name());
	    menuItem.addActionListener(this);
	    menu.add(menuItem);
	    menuItem = new JMenuItem("Solid");
	    menuItem.setActionCommand("setDisplayMode_" + AbstractViewport.RenderingMode.SOLID.name());
	    menuItem.addActionListener(this);
	    menu.add(menuItem);
	    menu.addSeparator();
	    menu.setEnabled(false);*/

	   /* chckShowWireframe = new JCheckBoxMenuItem("Show wireframe");
	    chckShowWireframe.setActionCommand("setShowWireframe");
	    chckShowWireframe.addActionListener(this);
	    menu.add(chckShowWireframe);

	    menu = new JMenu("Background");
	    menuItem = new JMenuItem("Load image...");
	    menuItem.setActionCommand("bckgrnd_loadimg");
	    menuItem.addActionListener(this);
	    menu.add(menuItem);
	    c.add(menu);*/
	}

	public void showPopupMenu(int x, int y){
		popupMenu.show(this, x, y);
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		String actCmd[] = e.getActionCommand().split("_", 2);
		if (actCmd[0].contentEquals("setProjection")) {
			setProjection(AbstractViewport.Projection.valueOf(actCmd[1]));
		} else if (actCmd[0].contentEquals("setView")) {
			setCamera(new Camera(PresetView.values()[Integer.parseInt(actCmd[1])]));
		} else if (actCmd[0].contentEquals("setDisplayMode")) {
			setRenderingMode(AbstractViewport.RenderingMode.valueOf(actCmd[1]));
		} else if (actCmd[0].contentEquals("setShowWireframe")) {
			// displayWireframe = chckShowWireframe.isSelected();
		}
		drawScene();
	}
	
	private void updateProjection(){
		if(oldWidth > 0 && oldHeight > 0){
			double changeRatio = Math.min((double)getWidth() / oldWidth, (double)getHeight() / oldHeight);
			if(changeRatio < 0.01)
				changeRatio = 1.0;
			orthProjectionScale /= changeRatio;
		}
		mtxProjection[0] = new Mat4OrthoRH(
				getWidth() * orthProjectionScale, getHeight() * orthProjectionScale, 0.001f, 1000);
		mtxProjection[1] = new Mat4PerspRH(
				Math.PI / 4, getHeight() / (double) Math.max(getWidth(), 1.f), 0.001f, 1000);
	}
	
	@Override
	public void componentResized(ComponentEvent e) {
		updateProjection();
		oldWidth = getWidth();
		oldHeight = getHeight();
	}
	@Override
	public void componentHidden(ComponentEvent e) {}
	@Override
	public void componentMoved(ComponentEvent e) {}
	@Override
	public void componentShown(ComponentEvent e) {}
}
